1use crate::copp::copp3::Topp3Profile;
28use crate::copp::copp3::formulation::{Topp3Problem, get_weight_a_topp3};
29use crate::copp::copp3::opt3::ClarabelExpertInfor3rd;
30use crate::copp::copp3::opt3::clarabel_constraints::{
31 clarabel_standard_capacity_topp3, clarabel_standard_constraint_topp3,
32};
33use crate::copp::{ClarabelOptions, clarabel_to_copp3_solution};
34use crate::diag::{
35 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
36 check_boundary_state_copp3_valid, check_s_interval_valid, format_duration_human,
37};
38use clarabel::algebra::CscMatrix;
39use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
40use core::f64;
41
42pub fn topp3_lp(
61 problem: &Topp3Problem,
62 options: &ClarabelOptions,
63) -> Result<Topp3Profile, CoppError> {
64 let (result, solution) = topp3_lp_expert(problem, options)?;
65 result.ok_or_else(|| CoppError::ClarabelSolverStatus("topp3_lp".into(), solution.status))
66}
67
68pub fn topp3_lp_expert(
94 problem: &Topp3Problem,
95 options: &ClarabelOptions,
96) -> Result<(Option<Topp3Profile>, DefaultSolution<f64>), CoppError> {
97 let info = topp3_lp_expert_with_info(problem, options)?;
98 let _ = &info.linsolver;
99 Ok((info.result, info.solution))
100}
101
102pub fn topp3_lp_expert_with_info(
112 problem: &Topp3Problem,
113 options: &ClarabelOptions,
114) -> Result<ClarabelExpertInfor3rd, CoppError> {
115 match options.verbosity() {
116 Verbosity::Silent => topp3_lp_core(problem, (options, SilentVerboser)),
117 Verbosity::Summary => topp3_lp_core(problem, (options, SummaryVerboser::new())),
118 Verbosity::Debug => topp3_lp_core(problem, (options, DebugVerboser::new())),
119 Verbosity::Trace => topp3_lp_core(problem, (options, TraceVerboser::new())),
120 }
121}
122
123fn topp3_lp_core(
134 problem: &Topp3Problem,
135 options_verboser: (&ClarabelOptions, impl Verboser),
136) -> Result<ClarabelExpertInfor3rd, CoppError> {
137 let (options, mut verboser) = options_verboser;
138 let idx_s_start = problem.idx_s_start;
139 let a_boundary = problem.a_boundary;
140 let b_boundary = problem.b_boundary;
141 let num_stationary = problem.num_stationary;
142 if verboser.is_enabled(Verbosity::Summary) {
143 verboser.record_start_time();
144 }
145 if verboser.is_enabled(Verbosity::Trace) {
146 let settings = options.clarabel_settings();
147 crate::verbosity_log!(
148 crate::diag::Verbosity::Summary,
149 "topp3_lp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
150 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
151 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
152 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
153 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
154 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
155 settings.tol_gap_rel,
156 settings.tol_feas,
157 settings.max_iter,
158 settings.verbose
159 );
160 }
161
162 check_boundary_state_copp3_valid(a_boundary, b_boundary)?;
164 let n = problem.a_linearization.len() - 1;
165 let idx_s_final = idx_s_start + n;
166 if verboser.is_enabled(Verbosity::Summary) {
167 crate::verbosity_log!(
168 crate::diag::Verbosity::Summary,
169 "\ntopp3_lp started: {} <= idx_s <= {}, s_len = {}, num_stationary={:?}.",
170 idx_s_start,
171 idx_s_final,
172 problem.a_linearization.len(),
173 num_stationary
174 );
175 }
176 check_s_interval_valid("topp3_lp", idx_s_start, idx_s_final)?;
177 let (capacity_val, capacity_b, capacity_cones) =
183 clarabel_standard_capacity_topp3(problem.constraints, (idx_s_start, idx_s_final));
184 if verboser.is_enabled(Verbosity::Debug) {
185 crate::verbosity_log!(
186 crate::diag::Verbosity::Summary,
187 "topp3_lp: capacity estimate standard(val={capacity_val}, b={capacity_b}, cone={capacity_cones}), n_var={}",
188 2 * (n + 1)
189 );
190 }
191 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(capacity_cones);
192 let mut row = Vec::<usize>::with_capacity(capacity_val);
193 let mut col = Vec::<usize>::with_capacity(capacity_val);
194 let mut val = Vec::<f64>::with_capacity(capacity_val);
195 let mut b = Vec::<f64>::with_capacity(capacity_b);
196 if verboser.is_enabled(Verbosity::Trace) {
197 crate::verbosity_log!(
198 crate::diag::Verbosity::Summary,
199 "topp3_lp: allocated capacities row/col/val/b/cones <= {capacity_val}/{capacity_val}/{capacity_val}/{capacity_b}/{capacity_cones}",
200 );
201 }
202
203 let s = problem.constraints.s_vec(idx_s_start, idx_s_final + 1)?;
205 let row_before_std = row.len();
206 let col_before_std = col.len();
207 let val_before_std = val.len();
208 let b_before_std = b.len();
209 let cones_before_std = cones.len();
210 clarabel_standard_constraint_topp3(
211 problem,
212 &s,
213 (&mut row, &mut col, &mut val, &mut b, &mut cones),
214 num_stationary,
215 &verboser,
216 )?;
217 if verboser.is_enabled(Verbosity::Trace) {
218 crate::verbosity_log!(
219 crate::diag::Verbosity::Summary,
220 "topp3_lp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
221 row.len() - row_before_std,
222 col.len() - col_before_std,
223 val.len() - val_before_std,
224 b.len() - b_before_std,
225 cones.len() - cones_before_std
226 );
227 }
228
229 let n_var = 2 * (n + 1);
231 let row_len = row.len();
232 let col_len = col.len();
233 let val_len = val.len();
234 let b_len = b.len();
235 let cones_len = cones.len();
236 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
237 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
239 let q_object = clarabel_q_object_topp3_lp(&s, num_stationary, n_var);
240 if verboser.is_enabled(Verbosity::Trace) {
241 let (q_min, q_max) = q_object
242 .iter()
243 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
244 (mn.min(v), mx.max(v))
245 });
246 crate::verbosity_log!(
247 crate::diag::Verbosity::Summary,
248 "topp3_lp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}, q_range=[{}, {}]",
249 b_len,
250 n_var,
251 a_csc.nnz(),
252 p_object.nnz(),
253 q_min,
254 q_max
255 );
256 }
257 if verboser.is_enabled(Verbosity::Summary) {
258 crate::verbosity_log!(
259 crate::diag::Verbosity::Summary,
260 "topp3_lp: ready to solve with row/col/val/b/cones = {row_len}/{col_len}/{val_len}/{b_len}/{cones_len} and n_var = {n_var}.",
261 );
262 }
263 let settings = options.clarabel_settings().clone();
265 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
266 .map_err(|e| CoppError::ClarabelSolverError("topp3_lp".into(), e))?;
267 solver.solve();
268 let linsolver = solver.info.linsolver.clone();
269 let solution = solver.solution;
270 if verboser.is_enabled(Verbosity::Summary) {
271 crate::verbosity_log!(
272 crate::diag::Verbosity::Summary,
273 "topp3_lp: solve done, status = {:?}, elapsed = {}.",
274 solution.status,
275 format_duration_human(verboser.elapsed())
276 );
277 }
278 if verboser.is_enabled(Verbosity::Trace) {
279 let show = solution.x.len().min(3);
280 crate::verbosity_log!(
281 crate::diag::Verbosity::Summary,
282 "topp3_lp: solution x_len={}, head={:?}",
283 solution.x.len(),
284 &solution.x[0..show]
285 );
286 }
287 let result = if options.is_allow(solution.status) {
288 Some(clarabel_to_copp3_solution(
289 &solution.x.as_slice()[0..2 * (n + 1)],
290 &s,
291 num_stationary,
292 ))
293 } else {
294 None
295 };
296 if verboser.is_enabled(Verbosity::Trace) {
297 crate::verbosity_log!(
298 crate::diag::Verbosity::Summary,
299 "topp3_lp: allow(status)={}, extracted_profile={}",
300 options.is_allow(solution.status),
301 if result.is_some() {
302 "Some(Topp3Profile)"
303 } else {
304 "None"
305 }
306 );
307 }
308 Ok(ClarabelExpertInfor3rd {
309 result,
310 solution,
311 linsolver,
312 })
313}
314
315#[inline(always)]
325fn clarabel_q_object_topp3_lp(s: &[f64], num_stationary: (usize, usize), n_var: usize) -> Vec<f64> {
326 let mut q_object = get_weight_a_topp3(s, num_stationary);
327 q_object.iter_mut().for_each(|q_i| *q_i = -*q_i);
329 q_object.resize(n_var, 0.0);
330 q_object
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::copp::ClarabelOptionsBuilder;
337 use crate::copp::InterpolationMode;
338 use crate::copp::copp2::stable::basic::{Topp2ProblemBuilder, s_to_t_topp2};
339 use crate::copp::copp2::stable::reach_set2::ReachSet2OptionsBuilder;
340 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
341 use crate::copp::copp3::stable::basic::{Topp3ProblemBuilder, s_to_t_topp3, t_to_s_topp3};
342 use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
343 use crate::robot::robot_core::Robot;
344 use std::time::Instant;
345
346 #[test]
347 fn test_topp3_lp() -> Result<(), CoppError> {
348 run_test_topp3_lp_repeated(1, false)
349 }
350
351 #[test]
354 #[ignore = "slow"]
355 fn test_topp3_lp_robust() -> Result<(), CoppError> {
356 run_test_topp3_lp_repeated(100, true)
357 }
358
359 fn run_one_topp3_lp_case(
360 options_lp: &ClarabelOptions,
361 ) -> Result<(f64, f64, f64, f64, f64, usize), CoppError> {
362 let n: usize = 1000;
363 let dim = 7;
364 let mut rng = rand::rng();
365 let (s, path, _, _) =
366 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
367
368 let mut robot = Robot::with_capacity(dim, n);
369 robot
370 .with_s(&s.as_view())?
371 .with_q_from_path_3rd(&path, 0, n)?;
372 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
373
374 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
375 let start = Instant::now();
376 let options_ra = ReachSet2OptionsBuilder::new()
377 .lp_feas_tol(1E-9)
378 .a_cmp_abs_tol(1E-9)
379 .a_cmp_rel_tol(1E-9)
380 .build()?;
381 let a_profile_ra = topp2_ra(&topp2_problem, &options_ra)?;
382 let time_topp_ra = start.elapsed().as_secs_f64() * 1E3;
383 let (t_motion_ra, _) = s_to_t_topp2(s.as_slice(), &a_profile_ra, 0.0)?;
384
385 let start = Instant::now();
386 robot.constraints.amax_substitute(&a_profile_ra, 0)?;
387 let topp3_problem =
388 Topp3ProblemBuilder::new(&mut robot, 0, &a_profile_ra, (0.0, 0.0), (0.0, 0.0))
389 .with_num_stationary_max(2)
390 .build_with_linearization()?;
391 let profile = topp3_lp(&topp3_problem, options_lp)?;
392 let time_topp3_lp = start.elapsed().as_secs_f64() * 1E3;
393 let start = Instant::now();
394 let (t_motion_lp, t_s) = s_to_t_topp3(s.as_slice(), profile.as_parts(), 0.0)?;
395 let s_t = t_to_s_topp3(
396 s.as_slice(),
397 profile.as_parts(),
398 &t_s,
399 InterpolationMode::UniformTimeGrid(0.0, 1E-3, true),
400 )?;
401 let time_interpolation = start.elapsed().as_secs_f64() * 1E3;
402 Ok((
403 time_topp_ra,
404 time_topp3_lp,
405 time_interpolation,
406 t_motion_ra,
407 t_motion_lp,
408 s_t.len(),
409 ))
410 }
411
412 fn run_test_topp3_lp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
413 let options_lp = ClarabelOptionsBuilder::new()
414 .allow_almost_solved(true)
415 .build()?;
416
417 let mut tc_sum_ra = 0.0;
418 let mut tc_sum_lp = 0.0;
419 let mut tf_sum_ra = 0.0;
420 let mut tf_sum_lp = 0.0;
421 for i_exp in 0..n_exp {
422 let (
423 time_topp_ra,
424 time_topp3_lp,
425 time_interpolation,
426 t_motion_ra,
427 t_motion_lp,
428 s_t_len,
429 ) = run_one_topp3_lp_case(&options_lp)?;
430
431 if flag_print_step {
432 crate::verbosity_log!(
433 crate::diag::Verbosity::Summary,
434 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_interpolation = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, s_t.len() = {}",
435 i_exp + 1,
436 time_topp_ra,
437 time_topp3_lp,
438 time_interpolation,
439 t_motion_ra,
440 t_motion_lp,
441 s_t_len,
442 );
443 }
444
445 tc_sum_ra += time_topp_ra;
446 tc_sum_lp += time_topp3_lp;
447 tf_sum_ra += t_motion_ra;
448 tf_sum_lp += t_motion_lp;
449 }
450
451 crate::verbosity_log!(
452 crate::diag::Verbosity::Summary,
453 "Average over {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}",
454 n_exp,
455 tc_sum_ra / n_exp as f64,
456 tc_sum_lp / n_exp as f64,
457 tf_sum_ra / n_exp as f64,
458 tf_sum_lp / n_exp as f64,
459 );
460
461 Ok(())
462 }
463}